﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.Data.SqlClient;

namespace Autorun
{
	[Serializable]
	public sealed class ScheduleManagement : MarshalByRefObject
	{
		#region "Private Members"
		private int sysLastPlannedDay;
		private SortedList<string, TaskContext> sysWaitingTasks;
		private Queue<TaskContext> sysWaitingTasksOnShutdown;
		private int _waitingTasksCountMax;
		private int _waitingTasksOnShutdownCountMax;
		#endregion

		#region "Singleton Design-Pattern"
		private static ScheduleManagement _instance = new ScheduleManagement();

		public static ScheduleManagement Instance
		{
			get { return _instance; }
		}

		private ScheduleManagement()
		{
			sysWaitingTasks = new SortedList<string, TaskContext>();
			sysWaitingTasksOnShutdown = new Queue<TaskContext>();
			sysLastPlannedDay = 0;
		}
		#endregion

		#region "Properties"
		public List<ScheduleItem> Items
		{
			get
			{
				string query;
				DataTable dt;
				List<ScheduleItem> rc;

				rc = new List<ScheduleItem>();
				query = "SELECT * FROM tblSchedule, tblTask WHERE tskId=fktskId";
				dt = ExecuteDataTable(query);
				foreach (DataRow dr in dt.Rows)
					rc.Add(new ScheduleItem(dr));

				return rc;
			}
		}

		internal SortedList<string, TaskContext> WaitingTasks
		{
			get { return sysWaitingTasks; }
		}

		internal Queue<TaskContext> WaitingTasksOnShutdown
		{
			get { return sysWaitingTasksOnShutdown; }
		}

		public int LastPlannedDay
		{
			get { return sysLastPlannedDay; }
		}

		public int WaitingTasksCount
		{
			get { return sysWaitingTasks.Count; }
		}

		public int WaitingTasksOnShutdownCount
		{
			get { return sysWaitingTasksOnShutdown.Count; }
		}

		public int WaitingTasksCountMax
		{
			get { return _waitingTasksCountMax; }
		}

		public int WaitingTasksOnShutdownCountMax
		{
			get { return _waitingTasksOnShutdownCountMax; }
		}

		public DateTime NextTask
		{
			get
			{
				lock (this)
				{
					if (sysWaitingTasks.Count == 0)
						return DateTime.MaxValue;
					else
						return ((TaskContext)sysWaitingTasks.Values[0]).PlannedOn;
				}
			}
		}
		#endregion

		#region "Methods"
		public ScheduleItem GetScheduleInformation(int scheduleId)
		{
			ScheduleItem rc;

			rc = new ScheduleItem(scheduleId);

			return rc;
		}

		public int Add(
										int taskId,
										ScheduleTypes type,
										DateTime startDate,
										DateTime startTime,
										DateTime finishDate,
										DateTime finishTime,
										TimeSpan repeat,
										ScheduleWeekDays runOnWeekDays,
										ScheduleMonths runOnMonths,
										int runOnDays,
										ScheduleRunOnDayOccurences runOnDayOccurence)
		{
			object rc;
			string query;

			///TODO: Déplacer ce code dans le New et l'Update de l'item
			query = string.Format("INSERT INTO tblSchedule (fktskId, schType, schStartDateTime, schFinishDate, schFinishTime, schRepeatEvery, schRunOnWeekDays, schRunOnMonths, schRunOnDays, schRunOnDayOccurence) VALUES ({0},{1},{2},{3},{4},{5},{6},{7},{8},{9}); SELECT @@IDENTITY FROM tblSchedule",
											taskId,
											(int)type,
											SqlConvert.SqlDate(new DateTime(startDate.Year, startDate.Month, startDate.Day, startTime.Hour, startTime.Minute, startTime.Second)),
											SqlConvert.SqlDate(new DateTime(finishDate.Year, finishDate.Month, finishDate.Day, 23, 59, 59)),
											SqlConvert.SqlDate(finishTime),
											repeat.Ticks,
											(int)runOnWeekDays,
											(int)runOnMonths,
											(int)runOnDays,
											(int)runOnDayOccurence);

			rc = ExecuteScalar(query);
			return int.Parse(rc.ToString());
		}

		public void Delete(int scheduleId)
		{
			string query;

			query = string.Format("DELETE FROM tblLog WHERE fkschId={0}", scheduleId);
			ExecuteNonQuery(query);

			query = string.Format("DELETE FROM tblSchedule WHERE schId={0}", scheduleId);
			ExecuteNonQuery(query);
		}

		internal void ResetPlanning()
		{
			sysWaitingTasks.Clear();
			sysWaitingTasksOnShutdown.Clear();
			sysLastPlannedDay = 0;
			LogManagement.Instance.FlushReport();
		}

		internal void BuildPlanning()
		{
			TaskConfigurationItem task;
			TaskContext context;
			DayRange dr;
			bool isLastDay;
			DateTime lastDate;
			bool thisMonth;
			bool thisDay1;
			bool thisDay2;
			ScheduleWeekDays thisDay;

			// =====================================================================
			// === Pre-Process                                                   ===
			// =====================================================================
			// === Do not build the planning in the same day =======================
			if (sysLastPlannedDay == DateTime.Now.Day) return;
			lastDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1).AddMonths(1).AddDays(-1);
			isLastDay = (lastDate.Day == DateTime.Now.Day);

			LogManagement.Instance.FlushReport();

			// =====================================================================
			// === Process                                                       ===
			// =====================================================================
			foreach (ScheduleItem item in Items)
			{
				if ((item.StartDateTime <= DateTime.Now) &&
												((item.FinishDate.Equals(DateTime.MaxValue) == true) || (item.FinishDate >= DateTime.Now)))
				{
					task = TaskConfigurationCollection.Instance.GetTaskInformation(item.TaskId);
					switch (item.Types)
					{
						case ScheduleTypes.Inactive:
							// --- Do not process inactive schedules ---------
							break;
						case ScheduleTypes.Startup:
							// --- Only if it is the first schedule ----------
							if ((ConfigurationManagement.Instance.ProcessStartupTasks == true) && (sysLastPlannedDay == 0))
							{
								context = new TaskContext(item.Id, item.TaskId, task.Path, task.AssemblyName, task.ClassName, item.Caption, DateTime.MinValue, task.Parameters);
								sysWaitingTasks.Add(context.Key, context);
							}
							break;
						case ScheduleTypes.Shutdown:
							// --- Only if it is the first schedule ----------
							if ((ConfigurationManagement.Instance.ProcessShutdownTasks == true) && (sysLastPlannedDay == 0))
							{
								context = new TaskContext(item.Id, item.TaskId, task.Path, task.AssemblyName, task.ClassName, item.Caption, DateTime.MaxValue, task.Parameters);
								sysWaitingTasksOnShutdown.Enqueue(context);
							}
							break;
						case ScheduleTypes.Daily:
							// --- Process a daily task ------------------------------------
							PlanTask(item.Id, item.TaskId, item.StartDateTime, item.FinishTime, item.RepeatEvery, task.Path, task.AssemblyName, task.ClassName, item.Caption, task.Parameters);
							break;
						case ScheduleTypes.Weekly:
							// --- Process a weekly task ---------------------
							switch (DateTime.Now.DayOfWeek)
							{
								case DayOfWeek.Monday:
									thisDay = ScheduleWeekDays.Monday;
									break;
								case DayOfWeek.Tuesday:
									thisDay = ScheduleWeekDays.Tuesday;
									break;
								case DayOfWeek.Wednesday:
									thisDay = ScheduleWeekDays.Wednesday;
									break;
								case DayOfWeek.Thursday:
									thisDay = ScheduleWeekDays.Thursday;
									break;
								case DayOfWeek.Friday:
									thisDay = ScheduleWeekDays.Friday;
									break;
								case DayOfWeek.Saturday:
									thisDay = ScheduleWeekDays.Saturday;
									break;
								case DayOfWeek.Sunday:
									thisDay = ScheduleWeekDays.Sunday;
									break;
								default:
									throw new Exception();
							}

							if ((((int)item.RunOnWeekDays & (int)thisDay)) == (int)thisDay)
								PlanTask(item.Id, item.TaskId, item.StartDateTime, item.FinishTime, item.RepeatEvery, task.Path, task.AssemblyName, task.ClassName, item.Caption, task.Parameters);
							break;
						case ScheduleTypes.Monthly:
							// --- Process a monthly task --------------------
							// Is the month ok?
							thisMonth = (((int)item.RunOnMonths & (int)Math.Pow(2, DateTime.Now.Month - 1)) != 0);

							// Is it one of the fixed days?
							dr = new DayRange(item.RunOnDays);
							thisDay1 = ((dr.Value[DateTime.Now.Day] == true) || ((isLastDay == true) && (dr.Value[32] == true)));

							// Is it one of the recurring days?
							thisDay2 = false;

							if ((thisMonth == true) && ((thisDay1 == true) || (thisDay2 == true)))
								PlanTask(item.Id, item.TaskId, item.StartDateTime, item.FinishTime, item.RepeatEvery, task.Path, task.AssemblyName, task.ClassName, item.Caption, task.Parameters);
							break;
						default:
							throw new Exception();
					}
				}
			}

			// =====================================================================
			// === Post-Process                                                  ===
			// =====================================================================
			sysLastPlannedDay = DateTime.Now.Day;
			_waitingTasksCountMax = sysWaitingTasks.Count;
			_waitingTasksOnShutdownCountMax = sysWaitingTasksOnShutdown.Count;
		}

		private void PlanTask(int scheduleId, int taskId, DateTime startTime, DateTime finishTime, TimeSpan repeatEvery, string assemblyPath, string assemblyName, string className, string caption, StringDictionary parameters)
		{
			DateTime dateOffset;
			DateTime dateMax;
			TaskContext context;

			dateOffset = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, startTime.Hour, startTime.Minute, 0);
			if (repeatEvery.Equals(TimeSpan.Zero) == true)
			{
				if ((dateOffset >= DateTime.Now) || (ConfigurationManagement.Instance.ProcessPassedTasks == true))
				{
					context = new TaskContext(scheduleId, taskId, assemblyPath, assemblyName, className, caption, dateOffset, parameters);
					sysWaitingTasks.Add(context.Key, context);
				}
			}
			else
			{
				dateMax = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, finishTime.Hour, finishTime.Minute, 0);
				while (dateOffset <= dateMax)
				{
					if ((dateOffset >= DateTime.Now) || (ConfigurationManagement.Instance.ProcessPassedTasks == true))
					{
						context = new TaskContext(scheduleId, taskId, assemblyPath, assemblyName, className, caption, dateOffset, parameters);
						sysWaitingTasks.Add(context.Key, context);
					}
					dateOffset = dateOffset.Add(repeatEvery);
				}
			}
		}

		public void PlanTask(int taskId)
		{
			TaskContext context;
			TaskConfigurationItem info;

			info = new TaskConfigurationItem(taskId);
			context = new TaskContext(0, taskId, info.Path, info.AssemblyName, info.ClassName, info.Caption, DateTime.Now, info.Parameters);
			sysWaitingTasks.Add(context.Key, context);
		}

		internal void PlanTask(TaskContext context)
		{
			sysWaitingTasks.Add(context.Key, context);
		}

		internal StringDictionary GetTaskParameters(int taskId)
		{
			StringDictionary rc;
			string query;
			DataTable dt;

			rc = new StringDictionary();

			// Add the properties of the task
			query = string.Format("SELECT * FROM tblConfig WHERE fktskId={0}", taskId);
			dt = ExecuteDataTable(query);
			foreach (DataRow dr in dt.Rows)
				rc.Add(dr["cfgKey"].ToString(), dr["cfgValue"].ToString());

			return rc;
		}

		internal StringDictionary MergeTaskParameters(int taskId, StringDictionary chainedParameters)
		{
			StringDictionary rc;

			rc = GetTaskParameters(taskId);

			if (chainedParameters != null)
				foreach (DictionaryEntry item in chainedParameters)
					rc[item.Key.ToString()] = item.Value.ToString();

			return rc;
		}
		#endregion

		#region "Data Access Methods"
		private void ExecuteNonQuery(string query)
		{
			SqlConnection cn;
			SqlCommand cmd;

			cn = new SqlConnection(Properties.Settings.Default.ConnectionString);

			try
			{
				cn.Open();
				cmd = cn.CreateCommand();
				cmd.CommandText = query;
				cmd.ExecuteNonQuery();
				cmd.Connection = null;
				cmd = null;
			}
			finally
			{
				cn.Close();
			}
		}

		private object ExecuteScalar(string query)
		{
			SqlConnection cn;
			SqlCommand cmd;
			object rc = null;

			cn = new SqlConnection(Properties.Settings.Default.ConnectionString);

			try
			{
				cn.Open();
				cmd = cn.CreateCommand();
				cmd.CommandText = query;
				rc = cmd.ExecuteScalar();
				cmd.Connection = null;
				cmd = null;
			}
			finally
			{
				cn.Close();
			}

			return rc;
		}

		private DataTable ExecuteDataTable(string query)
		{
			SqlDataAdapter da;
			DataSet rc;

			da = new SqlDataAdapter(query, Properties.Settings.Default.ConnectionString);
			rc = new DataSet();
			da.Fill(rc);
			return rc.Tables.Count != 0 ? rc.Tables[0] : null;
		}
		#endregion

	}
}
